/*
  ******************************************************************************
  name + matriculation number
  name + matriculation number
	
	In diesem Projekt gilt:
	*=============================================================================
  *        SYSCLK(Hz)                             | 64000000
  *-----------------------------------------------------------------------------
  *        AHB Prescaler                          | 1
  *-----------------------------------------------------------------------------
  *        APB2 Prescaler                         | 1
  *-----------------------------------------------------------------------------
  *        APB1 Prescaler                         | 2
  *=============================================================================
  ******************************************************************************
*/

#include "stm32f30x_conf.h"
#include "main.h"
#include "game.h"

// all globals

// SPI buffers
uint8_t tx_buffer[4];
uint8_t rx_buffer[4];

// SPI buffer indices
__IO uint32_t rx_idx = 0x00;
__IO uint32_t tx_idx = 0x00;

// framebuffer global
// to be used to draw in interrupt routine
// composed of pixels that can assume values between
// 0 and 3 (00, 01, 10, 11) with various meanings
// LED_IGNORE or 00 or 0 .. do nothing and read from addr
// LED_ENABLE or 01 or 1 .. enable LED at addr and read data from addr
// LED_DISABLE or 10 or 2 .. disable LED at addr and read data from addr
// LED_TOGGLE or 11 or 3 .. toggle LED at addr and read data from addr
uint8_t framebuffer[MAX_Y][MAX_X];
// used to store laser pointer information sent by controllers
// not needed anymore
//uint8_t activations[MAX_Y][MAX_X];
// general queue of game objects
LinkedList list;
// frame counter
uint32_t frames;
uint32_t framesMax;
// move counter
uint8_t moves;
uint8_t movesMax;
// shipsize (has to be variable because it can change)
uint8_t shipSize;
uint8_t lives;
uint8_t conversionValues[SAVED_ADC_CONVERSION_VALUES];
// game over screen constant
const uint16_t GOScreen[GO_SCREEN_SIZE] = {
// G. O.
//    0x1C30, 0x2048, 0x2048, 0x2C48, 0x2448, 0x1D32
// (very cool) skull
    0x1FF8, 0x3FFC, 0x3FFC, 0x799E, 0x718E, 0x73CE, 0x3FFC, 0x3FFC, 0x0E70, 0x07E0, 0x05A0
};

// entry point
int main(void) {
    // set up stm32 with peripherals
    STM32Config();
    // initialize game (particularly globals)
    gameInit();
    // initiate game loop
    while (1) __WFI();
}

// configures the stm32
// all other possible names are basically taken
static void STM32Config(void) {
    // general init structures
    GPIO_InitTypeDef        GPIO_InitStructure;
    SPI_InitTypeDef         SPI_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; 
    NVIC_InitTypeDef        NVIC_InitStructure;
    ADC_CommonInitTypeDef   ADC_CommonInitStructure;
    ADC_InitTypeDef         ADC_InitStructure;

	// enable AHB clock for periphery
    RCC_AHBPeriphClockCmd(
        RCC_AHBPeriph_GPIOA |
        RCC_AHBPeriph_GPIOB |
        RCC_AHBPeriph_GPIOC |
        SPIx_SCK_GPIO_CLK |
        SPIx_MISO_GPIO_CLK |
        SPIx_MOSI_GPIO_CLK |
        RCC_AHBPeriph_ADC12,
        ENABLE);

    // enable APB2 clock for periphery
    RCC_APB2PeriphClockCmd(SPIx_CLK | RCC_APB2Periph_TIM15, ENABLE);

    // enable APB1 clock for periphery
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 | RCC_APB1Periph_TIM3 , ENABLE);
    
    // config ADC and RCC (use PLL clock for max speed)
    RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div1);
    
    // configure necessary alternate functions
    GPIO_PinAFConfig(SPIx_SCK_GPIO_PORT, SPIx_SCK_SOURCE, SPIx_SCK_AF);
    GPIO_PinAFConfig(SPIx_MOSI_GPIO_PORT, SPIx_MOSI_SOURCE, SPIx_MOSI_AF);
    GPIO_PinAFConfig(SPIx_MISO_GPIO_PORT, SPIx_MISO_SOURCE, SPIx_MISO_AF);
    
    // init strobe pin
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
    GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // prepare struct for SPI pin configuration
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    // SPI SCK pin configuration
    GPIO_InitStructure.GPIO_Pin = SPIx_SCK_PIN;
    GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStructure);

    // SPI  MOSI pin configuration
    GPIO_InitStructure.GPIO_Pin =  SPIx_MOSI_PIN;
    GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStructure);

    // SPI MISO pin configuration
    GPIO_InitStructure.GPIO_Pin = SPIx_MISO_PIN;
    GPIO_Init(SPIx_MISO_GPIO_PORT, &GPIO_InitStructure);
    
    // init pin PA15 as strobe
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
    GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // init pin C0 as analog input
    // (unconnected but should still yield random values)
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
    GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    
    // SPI configuration
    SPI_I2S_DeInit(SPIx);
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_DataSize = SPI_DATASIZE;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    // unused
    SPI_InitStructure.SPI_CRCPolynomial = 0;

    TIM_TimeBaseStructInit(&TIM_TimeBaseInitStructure);
    TIM_TimeBaseInitStructure.TIM_Prescaler=1; // set to 32 MHz (prescaled already!)
    TIM_TimeBaseInitStructure.TIM_Period=52; // trigger after approx. 0.8us
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

    TIM_TimeBaseStructInit(&TIM_TimeBaseInitStructure);
    TIM_TimeBaseInitStructure.TIM_Prescaler=63; // set to 1 MHz
    TIM_TimeBaseInitStructure.TIM_Period=99; // trigger after 96us
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);

    TIM_TimeBaseStructInit(&TIM_TimeBaseInitStructure);
    TIM_TimeBaseInitStructure.TIM_Prescaler=49999; // set to 1280 Hz
    TIM_TimeBaseInitStructure.TIM_Period=19; // trigger with 64 Hz (64 FPS)
    TIM_TimeBaseInit(TIM15, &TIM_TimeBaseInitStructure);
    
    // ADC initialization routines
    ADC_CommonStructInit(&ADC_CommonInitStructure);
    ADC_CommonInitStructure.ADC_Clock = ADC_Clock_SynClkModeDiv1;
    ADC_CommonInit(ADC1, &ADC_CommonInitStructure);	
    ADC_StructInit(&ADC_InitStructure);
		
    // use continuous instead of discontinuous for starters
    ADC_InitStructure.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Enable;
    // use overrun to write new values even if they are not read
    ADC_InitStructure.ADC_OverrunMode = ADC_OverrunMode_Enable;	
    // pass init structure to ADC1
    ADC_Init(ADC1, &ADC_InitStructure);
    
    // configure ADC channel
    ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_7Cycles5);
    // use only one channel
    ADC_RegularChannelSequencerLengthConfig(ADC1, 1);
    // enable ADC voltage regulator
    ADC_VoltageRegulatorCmd(ADC1, ENABLE);

    // calibrate ADC1
    ADC_SelectCalibrationMode(ADC1, ADC_CalibrationMode_Single);
    ADC_StartCalibration(ADC1);
    // wait until done
    while(ADC_GetCalibrationStatus(ADC1) != RESET);

    // NVIC priority group/subgroup width config
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    // lower is more important for priorities
    NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel=TIM1_BRK_TIM15_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
    NVIC_Init(&NVIC_InitStructure);
		
    // configure the SPI interrupt priority
    NVIC_InitStructure.NVIC_IRQChannel = SPIx_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // have to wait 4 ADC clock cycles until calibration can be finished, do other work instead
    // (NVIC configuration should have taken enough)
    // finally start ADC
    ADC_Cmd(ADC1, ENABLE);
		
    // wait until ADC is ready to start conversion
    while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_RDY));
    // start converting values
    ADC_StartConversion(ADC1);

    // TIM2/3 config
    // TODO: change
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    TIM_ClearITPendingBit(TIM15, TIM_IT_Update);
    TIM_Cmd(TIM2, DISABLE);
    TIM_Cmd(TIM3, DISABLE);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
    TIM_ITConfig(TIM15, TIM_IT_Update, ENABLE);
    
    // initializes  the SPI communication
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_Init(SPIx, &SPI_InitStructure);

    // initialize the FIFO threshold
    SPI_RxFIFOThresholdConfig(SPIx, SPI_RxFIFOThreshold_QF);

    // enable the SPI peripheral and frame timer
    SPI_Cmd(SPIx, ENABLE);
    TIM_Cmd(TIM15, ENABLE);
}

/* ----------------- INTERRUPT HANDLERS -------------------*/
uint8_t TIM2Actived = 0;
void TIM2_IRQHandler(void)
{
    TIM2Actived = 1;
    // disable, not needed anymore
    TIM_Cmd(TIM2, DISABLE);
    // instead of disabling the interrupt we stop the timer
    //TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    
    // possible alternative to loop in 64 FPS timer
    /*
    draw
    strobe
    x++
    if x>XMAX
        y++
    if y>YMAX
        interrupt off
    return
    */
}

uint8_t TIM3Actived = 0;
void TIM3_IRQHandler(void)
{
    TIM3Actived = 1;
    // disable, not needed anymore
    TIM_Cmd(TIM3, DISABLE);
    //TIM_ITConfig(TIM3, TIM_IT_Update, DISABLE);
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}

// TIM interrupt for drawing
void TIM1_BRK_TIM15_IRQHandler(void)
{
    // query ADC for random values part 1
    conversionValues[0] = ADC_GetConversionValue(ADC1);
    
    // address to be calculated and sent to SPI devices
    static uint8_t address;
    TIM_ITConfig(TIM15, TIM_IT_Update, DISABLE);
    // enable SPI interrupt because we're going to need it
    SPI_I2S_ITConfig(SPIx, SPI_I2S_IT_RXNE, ENABLE);
    // render previously set framebuffer
    for (uint8_t y = 0; y < MAX_CONTROLLER_Y; y++) {
        for (uint8_t x = 0; x < MAX_CONTROLLER_X; x++) {
            // reset indices
            tx_idx = 0;
            rx_idx = 0;
            
            // controller arrangement:
            // M -> C0 -> ... -> C3
            //     LSB -> ... -> MSB
            address = 0x00 | ((y * MAX_CONTROLLER_Y + x) << 2);
            
            // SPI transmit buffer to prepare data to be sent
            // (actually sent in SPI TXE interrupt handler)
            tx_buffer[0] = address | framebuffer[y + MAX_CONTROLLER_Y][x + MAX_CONTROLLER_X];
            tx_buffer[1] = address | framebuffer[y                   ][x + MAX_CONTROLLER_X];
            tx_buffer[2] = address | framebuffer[y                   ][x                   ];
            tx_buffer[3] = address | framebuffer[y + MAX_CONTROLLER_Y][x                   ];
            
            // enable SPI1 TXE interrupt (gets disabled in handler)
            SPI_I2S_ITConfig(SPIx, SPI_I2S_IT_TXE, ENABLE);

            // (busy) wait on SPI_TXE_IRQHandler
            while (tx_idx < 4 || rx_idx < 4) __WFI();
            
            // set strobe pin to 1
            GPIO_SetBits(GPIOA, GPIO_Pin_15);
            // .8us wait
            TIM2Actived = 0;
            TIM_SetCounter(TIM2, 0);
            TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
            // manipulate timer instead
            //TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
            TIM_Cmd(TIM2, ENABLE);
            
            // nothing more can be done until TIM2 has finished counting
            // (very short timer anyways)
            while (TIM2Actived != 1) __WFI();
            // set strobe pin to 0
            GPIO_ResetBits(GPIOA, GPIO_Pin_15);
            
            // 96us wait
            TIM3Actived = 0;
            TIM_SetCounter(TIM3, 0);
            TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
            // manipulate timer instead
            //TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
            TIM_Cmd(TIM3, ENABLE);
            
            // immediately handle collision detection in the surplus time we have
            // by translating receive buffer data to collisions
            rxToCollision();
            
            // nothing more can be done until TIM3 has finished counting
            while (TIM3Actived != 1) __WFI();
        }
    }
    
    // communication is finished, disable interrupts
    SPI_I2S_ITConfig(SPIx, SPI_I2S_IT_TXE, DISABLE);
	SPI_I2S_ITConfig(SPIx, SPI_I2S_IT_RXNE, DISABLE);
    			
    // activities other than drawing
    TIM_ClearITPendingBit(TIM15, TIM_IT_Update);
        
    // query ADC for random values part 2
    conversionValues[1] = ADC_GetConversionValue(ADC1);
    
    // update game
    gameUpdate();
    
    // enable TIM15 again, so that the next frame can fire
    TIM_ITConfig(TIM15, TIM_IT_Update, ENABLE);
    
    // query ADC for random values part 3
    conversionValues[2] = ADC_GetConversionValue(ADC1);
}

void rxToCollision(void) {
    // address can only have 6 bits
    // lower 3 bits denote x value
    uint8_t maskX = ((rx_buffer[0] >> 2) & 0x7);
    // upper 3 bits denote y value
    uint8_t maskY = ((rx_buffer[0] >> 2) >> 3);
    // check M3
    if ((0x3 & rx_buffer[0]) == 1)
        checkCollision(MAX_CONTROLLER_Y + maskY, MAX_CONTROLLER_X + maskX);
    // check M2
    if ((0x3 & rx_buffer[1]) == 1)
        checkCollision(                   maskY, MAX_CONTROLLER_X + maskX);
    // check M1
    if ((0x3 & rx_buffer[2]) == 1)
        checkCollision(                   maskY,                    maskX);
    // check M0
    if ((0x3 & rx_buffer[3]) == 1)
        checkCollision(MAX_CONTROLLER_Y + maskY,                    maskX);
}

void SPI1_IRQHandler(void) {    
    // SPI transmit buffer empty interrupt (old send logic)
    if (SPI_I2S_GetITStatus(SPIx, SPI_I2S_IT_TXE) == SET) {
        // handle 4 bytes of transmit buffer
        if (tx_idx < 4) {
            // send actual data
            SPI_SendData8(SPIx, tx_buffer[tx_idx++]);
            // if tx_idx was incremented to 4, we've successfully handled 4 bytes
            if (tx_idx == 4) {
                SPI_I2S_ITConfig(SPIx, SPI_I2S_IT_TXE, DISABLE);
            }
        } 
    }
   
    // SPI receive buffer not empty interrupt
    if (SPI_I2S_GetITStatus(SPIx, SPI_I2S_IT_RXNE) == SET)
        rx_buffer[rx_idx++] = SPI_ReceiveData8(SPIx);

    // SPI error interrupt (not needed)
    //if (SPI_I2S_GetITStatus(SPIx, SPI_I2S_IT_OVR) == SET) {}
}
